﻿#region COPYRIGHT© 2005-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;

namespace FlitBit.Parse
{
	/// <summary>
	///   Captures a sequence of input during a match operation.
	/// </summary>
	public struct Capture : IEquatable<Capture>
	{
		/// <summary>
		///   Special invalid capture.
		/// </summary>
		public static readonly Capture Invalid = new Capture(Cursor.Invalid);

		static readonly int CHashCodeSeed = typeof(Capture).AssemblyQualifiedName.GetHashCode();

		readonly string _description;

		readonly Cursor _position;
		readonly Match _rule;
		readonly string _value;

		private Capture(Cursor invalidCursor)
		{
			_rule = null;
			_position =invalidCursor;
			_value = null;
			_description = null;
		}

		/// <summary>
		///   Creates a new instance
		/// </summary>
		/// <param name="rule"></param>
		/// <param name="position"></param>
		/// <param name="value"></param>
		public Capture(Match rule, Cursor position, string value)
		{
			Contract.Requires(rule != null, "rule must not be null");
			Contract.Requires(position.IsValid, "position must be valid");
			Contract.Requires(value != null, "value must not be null");

			_rule = rule;
			_position = position;
			_value = value;
			_description = null;
		}

		/// <summary>
		///   Creates a new instance.
		/// </summary>
		/// <param name="rule"></param>
		/// <param name="position"></param>
		/// <param name="value"></param>
		/// <param name="description"></param>
		public Capture(Match rule, Cursor position, string value, string description)
		{
			Contract.Requires(rule != null, "rule must not be null");
			Contract.Requires(position.IsValid, "position must be valid");
			Contract.Requires(value != null, "value must not be null");

			_rule = rule;
			_position = position;
			_value = value;
			_description = description;
		}

		/// <summary>
		///   Creates a new instance.
		/// </summary>
		/// <param name="rule"></param>
		/// <param name="position"></param>
		/// <param name="value"></param>
		public Capture(Match rule, Cursor position, char[] value)
		{
			Contract.Requires(rule != null, "rule must not be null");
			Contract.Requires(position.IsValid, "position must be valid");
			Contract.Requires(value != null, "value must not be null");

			_rule = rule;
			_position = position;
			_value = new string(value);
			_description = null;
		}

		/// <summary>
		///   Creates a new instance.
		/// </summary>
		/// <param name="rule"></param>
		/// <param name="position"></param>
		/// <param name="value"></param>
		/// <param name="description"></param>
		public Capture(Match rule, Cursor position, char[] value, string description)
		{
			Contract.Requires(rule != null, "rule must not be null");
			Contract.Requires(position.IsValid, "position must be valid");
			Contract.Requires(value != null, "value must not be null");

			_rule = rule;
			_position = position;
			_value = new string(value);
			_description = description;
		}

		/// <summary>
		///   A description of the capture.
		/// </summary>
		public string Description { get { return _description; } }

		/// <summary>
		///   Indicates whether the capture is invalid.
		/// </summary>
		public bool IsValid { get { return _rule != null; } }

		/// <summary>
		///   The capture's length.
		/// </summary>
		public int Length { get { return (_value != null) ? _value.Length : 0; } }

		/// <summary>
		///   The capture's position.
		/// </summary>
		public Cursor Position { get { return _position; } }

		/// <summary>
		///   The capture's rule. This is the match rule from which the capture was created.
		/// </summary>
		public Match Rule { get { return _rule; } }

		/// <summary>
		///   The capture's value.
		/// </summary>
		public string Value { get { return _value; } }

		/// <summary>
		/// Indicates whether this instance and a specified object are equal.
		/// </summary>
		/// <returns>
		/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
		/// </returns>
		/// <param name="obj">Another object to compare to. </param><filterpriority>2</filterpriority>
		public override bool Equals(object obj)
		{
			return obj is Capture
				&& Equals((Capture) obj);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this instance.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		public override int GetHashCode()
		{
			const int prime = 999067; // a random prime

			var result = CHashCodeSeed * prime;
			if (_rule != null)
			{
				result ^= prime * _rule.GetHashCode();
			}
			result ^= prime * _position.GetHashCode();
			if (_value != null)
			{
				result ^= prime * _value.GetHashCode();
			}
			if (_description != null)
			{
				result ^= prime * _description.GetHashCode();
			}
			return result;
		}

		/// <summary>
		/// Returns the fully qualified type name of this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> containing a fully qualified type name.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		public override string ToString()
		{
			return new StringBuilder(65)
				.Append("{ Value: ")
				.Append(_value.DoubleQuote())
				.Append(", Description: ")
				.Append(_description.DoubleQuote())
				.Append(", Position: ")
				.Append(_position.ToString())
				.Append(", Rule: ")
				.Append(_rule)
				.Append("}")
				.ToString();
		}

		/// <summary>
		/// Determines if the capture spans a line and position.
		/// </summary>
		/// <param name="line"></param>
		/// <param name="linepos"></param>
		/// <returns></returns>
		public bool ContainsPos(int line, int linepos)
		{
			var c = _position;
			return c.Line == line && linepos >= c.Column && linepos < (c.Column + Length);
		}

		/// <summary>
		/// Equality operator; determines if two captures are equal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator ==(Capture lhs, Capture rhs)
		{
			return lhs.Equals(rhs);
		}

		/// <summary>
		/// Inequality operator; determines if two captures are inequal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator !=(Capture lhs, Capture rhs)
		{
			return !lhs.Equals(rhs);
		}

		#region IEquatable<Capture> Members

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <returns>
		/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
		/// </returns>
		/// <param name="other">An object to compare with this object.</param>
		public bool Equals(Capture other)
		{
			return _rule.Equals(other._rule)
				&& _position == other.Position
				&& String.Equals(_value, other._value)
				&& String.Equals(_description, other._description);
		}

		#endregion

		/// <summary>
		/// Combines two captures into one.
		/// </summary>
		/// <typeparam name="TKind"></typeparam>
		/// <param name="newRule"></param>
		/// <param name="tokens"></param>
		/// <returns></returns>
		public static Capture Combine<TKind>(Match newRule, params Token<TKind>[] tokens)
			where TKind : struct
		{
			return Combine(newRule, (IEnumerable<Token<TKind>>) tokens);
		}

		/// <summary>
		/// Combines many captures into one.
		/// </summary>
		/// <typeparam name="TKind"></typeparam>
		/// <param name="newRule"></param>
		/// <param name="tokens"></param>
		/// <returns></returns>
		public static Capture Combine<TKind>(Match newRule, IEnumerable<Token<TKind>> tokens)
			where TKind : struct
		{
			Contract.Requires<ArgumentNullException>(tokens != null);
			var tks = tokens as Token<TKind>[] ?? tokens.ToArray();
			var sb = new StringBuilder(200);
			foreach (var tk in tks)
			{
				sb.Append(tk.Capture.Value);
			}
			return new Capture(newRule, tks.First()
																				.Capture.Position, sb.ToString());
		}

		/// <summary>
		/// Combines many captures into one.
		/// </summary>
		/// <typeparam name="TKind"></typeparam>
		/// <param name="newRule"></param>
		/// <param name="tokens"></param>
		/// <param name="description"></param>
		/// <returns></returns>
		public static Capture Combine<TKind>(Match newRule, IEnumerable<Token<TKind>> tokens, string description)
			where TKind : struct
		{
			Contract.Requires<ArgumentNullException>(tokens != null);
			var tks = tokens as Token<TKind>[] ?? tokens.ToArray();
			var sb = new StringBuilder(200);
			foreach (var tk in tks)
			{
				sb.Append(tk.Capture.Value);
			}
			return new Capture(newRule, tks.First()
																				.Capture.Position, sb.ToString(), description);
		}

		
	}
}